#include <fstream>
//=====================================================================
#include <boost/filesystem.hpp>
#include <boost/program_options.hpp>
//=====================================================================
#include <UnitTest++.h>
//=====================================================================
#include <atma/assert.hpp>
//=====================================================================
#include <kaleidoscope/frontend/lexical_analysis/lexer.hpp>
#include <kaleidoscope/frontend/syntactical_analysis/parser.hpp>
#include <kaleidoscope/frontend/semantic_analysis/environment_creator.hpp>
#include <kaleidoscope/frontend/semantic_analysis/semantic_analysis_pass.hpp>
#include <kaleidoscope/frontend/semantic_analysis/scope.hpp>
#include <kaleidoscope/backend/final_generator.hpp>
//=====================================================================
namespace bfs = boost::filesystem;
namespace bpo = boost::program_options;
//=====================================================================
void command_line(const std::string& command) {
	system(command.c_str());
}
//=====================================================================


struct commandline_representation
{
	friend commandline_representation parse_commandline(int, char **);
private:
	bpo::variables_map vm_;
	std::vector<std::string> sourcefiles_;
	bool bad_;
	
public:
	bool bad() const;
	bool help() const;
	bool run_unittests() const;
	const std::vector<std::string>& sourcefiles() const;
	void print_usage() const;
};


commandline_representation parse_commandline(int, char **);





//=====================================================================



int main(int arg_count, char ** args)
{
	//=====================================================================
	// 1) parse command-line stuff
	//=====================================================================
	commandline_representation cmlr = parse_commandline(arg_count, args);
	
	// print help if required
	if (cmlr.bad() || cmlr.help())
	{
		cmlr.print_usage();
		return EXIT_SUCCESS;
	}
	// run unit-tests if required
	else if (cmlr.run_unittests())
	{
		atma::assert::set_handler(&atma::assert::exit_failure_handler);
		return UnitTest::RunAllTests();
	}
	// exit if required :(
	else if (cmlr.sourcefiles().empty())
	{
		return EXIT_SUCCESS;
	}
	
	
	//=====================================================================
	// 2) figure out filenames and stuff
	//=====================================================================
	//bfs::path user_filepath = cmlr.sourcefiles.front();
	std::string user_filename = cmlr.sourcefiles().front();
	
	std::string base_filename = 
		std::string( user_filename.begin(), 
			std::find( user_filename.begin(), user_filename.end(), '.' ) );
	
	//bfs::path p = bfs::current_path();
	bfs::path input_filename = user_filename;
	bfs::path output_filename = base_filename + ".asm";
	bfs::path object_filename = base_filename + ".obj";
	
	if ( !bfs::exists(input_filename) )
	{
		input_filename = bfs::current_path() / input_filename;
		if ( !bfs::exists(input_filename) )
		{
			std::cout << "File " << input_filename << " doesn't exist! :(" << std::endl;
			return EXIT_FAILURE;
		}
	}
	
	
	//std::string output_filename = "../resource/output";
	
	
//	size_t k = 1 << 31;
	
	using namespace std;
	using namespace atma::sooty;
	
	// open up the file, and pass it to our lexer
	fstream file(input_filename.string().c_str(), fstream::binary | fstream::in);
	ATMA_ASSERT(file.is_open());
	
	//=====================================================================
	// 1) lexing
	//=====================================================================
	kal::main_lexer my_lexer(file);
	my_lexer.lex_input();
	
	//=====================================================================
	// 2) parsing
	//=====================================================================
	kal::main_parser my_parser(my_lexer.retrieve_lexemes());
	my_parser.parse();
	// for the debugging!
	my_parser.print_tree();
	
	//=====================================================================
	// 3) being semantical
	//=====================================================================
	int errors = kal::semantic_analysis::pass( my_parser.root_node() );
	
	
	// backend
	if (errors == 0)
	{
		//
		//
		//// generation
		bfs::path output_path = bfs::current_path() / output_filename;
		fstream output_file( output_path.string().c_str(), fstream::out | fstream::trunc);
		kal::generator::final_generator my_generator(my_parser.root_node());
		my_generator.go(&output_file);
		output_file.close();
		
		// assembly
		command_line("nasm -f win32 -iD:/dev/libraries/nasmx/ " + output_filename.string());
		
		// linking
		command_line("golink \"" + object_filename.string() + "\" kernel32.dll /entry main /console");
		
		// run it!
		command_line("D:/dev/projects/kaleidoscope/branch/resource/go.bat");
		command_line("echo %errorlevel%");
		
		std::cin.get();
		
		
		
	}
	
	
	
	std::cin.get();
}


//=====================================================================
bool commandline_representation::bad() const
{
	return bad_;
}

bool commandline_representation::help() const
{
	return vm_.count("help") > 0;
}

bool commandline_representation::run_unittests() const
{
	return vm_.count("runtests") > 0;
}

const std::vector<std::string>& commandline_representation::sourcefiles() const
{
	return sourcefiles_;
}

void commandline_representation::print_usage() const
{
	std::cout << "Usage: [ --runtests ] files..." << std::endl;
}





commandline_representation parse_commandline(int arg_count, char ** args)
{
	commandline_representation cmlr;
	
	// all options
	bpo::options_description desc("Allowed Options");
	desc.add_options()
		("runtests", "runs unit testing framework")
		("sourcefile", bpo::value< std::vector<std::string> >(), "input file");

	// position options
	bpo::positional_options_description positional_desc;
	positional_desc.add("sourcefile", -1);

	bpo::variables_map vm;
	
	cmlr.bad_ = false;
	
	try {
		bpo::store(
			bpo::command_line_parser(arg_count, args)
			.options(desc)
			.positional(positional_desc)
			.run(),
			vm);

		bpo::notify(vm);
	}
	catch (std::exception&) {
		std::cerr << desc << std::endl;
		cmlr.bad_ = true;
	}
	catch (...) {
		cmlr.bad_ = true;
	}

	cmlr.vm_ = vm;
	
	if (vm.count("sourcefile"))
		cmlr.sourcefiles_ = vm["sourcefile"].as<std::vector<std::string> >();
	
	return cmlr;
}

